Part 8 - Plans的介绍

背景

我们在这里介绍一个对于扩展工业级联邦学习至关重要的对象:计划。它大大减少了带宽使用,允许使用异步方案,并为远程设备提供了更多的自治权。计划的原始概念可以在论文大规模联合学习:系统设计中找到,但已在PySyft库中适应了我们的需求。

计划旨在像函数一样存储一系列的Torch操作,但它可以将该序列的操作发送给远程工作者,并保留对其的引用。这样,要对通过指针引用的某些远程输入上的 $n$ 操作序列进行远程计算,您现在需要发送包含计划的引用和指针的单个消息,而不是发送 $n$ 个消息。您还可以为函数提供张量(我们称为state tensors)以具有扩展的功能。可以将计划视为可以发送的函数,也可以视为可以远程发送和执行的类。因此,对于高级用户而言,计划的概念消失了,并被魔术功能所取代,该魔术功能允许向远程工作人员发送包含一系列任意的Torch函数。

需要注意的一件事是,您可以转换为计划的功能类别目前仅限于挂钩的Torch操作序列。即使我们正在努力尽快找到解决方法,这也特别排除了诸如ifforwhile语句之类的逻辑结构。 要完全精确,您可以使用这些,但是在计划的第一次计算中采用的逻辑路径(例如,第一个if到False和for中的5个循环)将是所有后续计算中保留的逻辑路径,在大多数情况下,我们都希望避免这种情况。

作者:

中文版译者:

导入包和模型规格

首先导入官方包,


In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F

与PySyft专用部分相比,有一个重要说明:本地工作机不应该是客户工作机。 因为非客户工作人员可以存储对象,我们需要这种能力来运行计划。


In [ ]:
import syft as sy  # import the Pysyft library
hook = sy.TorchHook(torch)  # hook PyTorch ie add extra functionalities 

# 重要: 本地工作机不应该是客户工作机
hook.local_worker.is_client_worker = False


server = hook.local_worker

我们定义远程工作机或devices,以与参考文章中提供的概念一致。 我们为他们提供一些数据。


In [ ]:
x11 = torch.tensor([-1, 2.]).tag('input_data')
x12 = torch.tensor([1, -2.]).tag('input_data2')
x21 = torch.tensor([-1, 2.]).tag('input_data')
x22 = torch.tensor([1, -2.]).tag('input_data2')

device_1 = sy.VirtualWorker(hook, id="device_1", data=(x11, x12)) 
device_2 = sy.VirtualWorker(hook, id="device_2", data=(x21, x22))
devices = device_1, device_2

基本例子

让我们定义一个我们想要转换为计划的函数。为此,就像在函数定义上方添加装饰器一样简单!


In [ ]:
@sy.func2plan()
def plan_double_abs(x):
    x = x + x
    x = torch.abs(x)
    return x

我们检查一下,是的现在有了一个计划!


In [ ]:
plan_double_abs

要使用计划,您需要做两件事:构建计划(_注册功能中存在的操作序列)并将其发送给工作机/设备。幸运的是,您可以轻松完成此操作!

构建计划

要构建计划,您只需要对一些数据进行调用。

首先让我们获取一些远程数据的引用:通过网络发送请求,并返回引用指针。


In [ ]:
pointer_to_data = device_1.search('input_data')[0]
pointer_to_data

如果我们告诉计划,它必须在devicelocation:device_1上远程执行…,我们将收到错误消息因为计划尚未建立。


In [ ]:
plan_double_abs.is_built
# 这个单元不能运行 plan_double_abs.send(device_1)

要构建一个计划,您只需要在该计划上调用build并传递执行该计划所需的参数(也就是一些数据)。当构建一个计划时,所有命令都由本地工作人员顺序执行,并被该计划捕获并存储在其read_plan属性中!


In [ ]:
plan_double_abs.build(torch.tensor([1., -2.]))

In [ ]:
plan_double_abs.is_built

现在尝试发送,它正常工作了。


In [ ]:
# 这个单元成功运行
pointer_plan = plan_double_abs.send(device_1)
pointer_plan

与随后的张量一样,我们获得指向发送对象的指针。这里简称为 PointerPlan

要记住的重要一件事是,在构建计划时,我们会在计算之前预先设置应存储结果的ID,这将允许异步发送命令,已经具有对虚拟结果的引用,并且可以继续本地计算,而不必等待远程结果被计算出来。一个主要的应用是当您需要在device_1上进行批处理计算,而又不想等待该计算结束以在device_2上启动另一批处理计算时。

远程运行计划

现在,我们可以通过使用指向某些数据的指针来调用该计划的指针来远程运行该计划。这发出了一个命令以远程运行该计划,因此计划输出的预定义位置现在包含结果(请记住,我们在计算之前预先设置了结果位置)。这也需要一次交流。

结果只是一个指针,就像调用一个普通被钩的Torch函数一样!


In [ ]:
pointer_to_result = pointer_plan(pointer_to_data)
print(pointer_to_result)

您可以简单地拿回值。


In [ ]:
pointer_to_result.get()

面向一个具体的例子

但是我们要做的是将计划应用于深度联邦学习,对吗? 因此,让我们看一个更复杂的示例,使用神经网络,因为您可能愿意使用它们。 请注意,我们现在正在将一个类转换为一个计划。 为此,我们从sy.Plan继承我们的类(而不是从nn.Module继承)。


In [ ]:
class Net(sy.Plan):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(2, 3)
        self.fc2 = nn.Linear(3, 2)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=0)

In [ ]:
net = Net()

In [ ]:
net

让我们使用一些模拟数据来构建计划。


In [ ]:
net.build(torch.tensor([1., 2.]))

现在,我们将计划发送给远程工作机。


In [ ]:
pointer_to_net = net.send(device_1)
pointer_to_net

让我们检索一些远程数据。


In [ ]:
pointer_to_data = device_1.search('input_data')[0]

然后,语法就像正常的远程顺序执行,也就是本地执行一样。但是,与传统的远程执行相比,每次执行仅进行一次通信。


In [ ]:
pointer_to_result = pointer_to_net(pointer_to_data)
pointer_to_result

我们照常得到结果!


In [ ]:
pointer_to_result.get()

等等! 我们已经看到了如何显着减少本地工作机(或服务器)与远程设备之间的通信!

在工作机之间切换

我们希望拥有的一个主要功能是对多个工作机使用相同的计划,我们将根据正在考虑的远程数据批次进行更改。 特别是,我们不想每次更换工作机时都重新构建计划。让我们使用小型网络的上一个示例,看看我们如何做到这一点。


In [ ]:
class Net(sy.Plan):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(2, 3)
        self.fc2 = nn.Linear(3, 2)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=0)

In [ ]:
net = Net()

# 建立计划
net.build(torch.tensor([1., 2.]))

这是我们刚刚执行的主要步骤


In [ ]:
pointer_to_net_1 = net.send(device_1)
pointer_to_data = device_1.search('input_data')[0]
pointer_to_result = pointer_to_net_1(pointer_to_data)
pointer_to_result.get()

实际上,您可以从同一计划构建其他PointerPlan,因此语法与在另一台设备上远程运行计划的语法相同


In [ ]:
pointer_to_net_2 = net.send(device_2)
pointer_to_data = device_2.search('input_data')[0]
pointer_to_result = pointer_to_net_2(pointer_to_data)
pointer_to_result.get()

注意:当前,对于Plan类,您只能使用一种方法,并且必须将其命名为"forward"。

自动构建计划函数

对于函数(@sy.func2plan),我们可以自动构建计划,而无需显式调用build,实际上是在创建时就已经构建了计划。

为了获得此功能,创建计划时您唯一需要更改的就是将参数设置为名为args_shape的装饰器,该参数应为包含每个参数形状的列表。


In [ ]:
@sy.func2plan(args_shape=[(-1, 1)])
def plan_double_abs(x):
    x = x + x
    x = torch.abs(x)
    return x

plan_double_abs.is_built

参数args_shape在内部用于创建具有给定形状的模拟张量,这些张量用于构建计划。


In [ ]:
@sy.func2plan(args_shape=[(1, 2), (-1, 2)])
def plan_sum_abs(x, y):
    s = x + y
    return torch.abs(s)

plan_sum_abs.is_built

您还可以为函数提供状态元素!


In [ ]:
@sy.func2plan(args_shape=[(1,)], state=(torch.tensor([1]), ))
def plan_abs(x, state):
    bias, = state.read()
    x = x.abs()
    return x + bias

In [ ]:
pointer_plan = plan_abs.send(device_1)
x_ptr = torch.tensor([-1, 0]).send(device_1)
p = pointer_plan(x_ptr)
p.get()

要了解更多信息,您可以在教程Part 8-bias中发现我们如何使用带有协议的计划!

恭喜!!! 是时候加入社区了!

祝贺您完成本笔记本教程! 如果您喜欢此方法,并希望加入保护隐私、去中心化AI和AI供应链(数据)所有权的运动,则可以通过以下方式做到这一点!

给 PySyft 加星

帮助我们的社区的最简单方法是仅通过给GitHub存储库加注星标! 这有助于提高人们对我们正在构建的出色工具的认识。

选择我们的教程

我们编写了非常不错的教程,以更好地了解联合学习和隐私保护学习的外观,以及我们如何为实现这一目标添砖加瓦。

加入我们的 Slack!

保持最新进展的最佳方法是加入我们的社区! 您可以通过填写以下表格来做到这一点http://slack.openmined.org

加入代码项目!

对我们的社区做出贡献的最好方法是成为代码贡献者! 您随时可以转到PySyft GitHub的Issue页面并过滤“projects”。这将向您显示所有概述,选择您可以加入的项目!如果您不想加入项目,但是想做一些编码,则还可以通过搜索标记为“good first issue”的GitHub问题来寻找更多的“一次性”微型项目。

捐赠

如果您没有时间为我们的代码库做贡献,但仍想提供支持,那么您也可以成为Open Collective的支持者。所有捐款都将用于我们的网络托管和其他社区支出,例如黑客马拉松和聚会!

OpenMined's Open Collective Page


In [ ]: